using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using System.Data;
using System.Text.RegularExpressions;
using TST.SharePoint.Shared.Datasource;
using Microsoft.SharePoint.Publishing;
using System.Globalization;

namespace TST.SharePoint.Shared
{
    /// <summary>
    /// Datasource implementation for getting data from SharePoint based on content type.
    /// Queries the current site collection for list items / documents of the specified
    /// content type and basetype. This basetype is required by the query object that actually
    /// queries the database.
    /// </summary>
    public class DatasourceContentType : GridDatasource
    {
        private string _contentTypeName;
        private SPWeb _web;
        private SPContentType _contentType;
        private SPBaseType _baseType = SPBaseType.UnspecifiedBaseType;
        private const string DisplayFormUrl = "_layouts/CopyUtil.aspx?Use=id&Action=dispform&ItemId={0}&ListId={1}&WebId={2}&SiteId={3}";
        private const string UserLinkUrl = "_layouts/userdisp.aspx?ID={0}";
        private bool _includeDescending;
        private ContentByTypeScope _scope;
        private string _listServerTemplate;

        /// <summary>
        /// Constructor.
        /// </summary>
        public DatasourceContentType()
        {
            Id = GridDatasource.IDDATASOURCECONTENTTYPE;
        }

        /// <summary>
        /// Get the selected content type (if there is one).
        /// </summary>
        private SPContentType ContentType
        {
            get
            {
                if (((_contentType == null) && !string.IsNullOrEmpty(_contentTypeName)) || 
                    (string.Compare(_contentType.Name, _contentTypeName, true)!=0))
                {
                    _contentType = Web.AvailableContentTypes[_contentTypeName];
                }
                return _contentType;
            }
        }

        public SPWeb Web
        {
            get
            {
                if (_web == null)
                    return SPContext.Current.Web;
                return _web;
            }
            set
            {
                _web = value;
            }
        }

        public string ContentTypeName
        {
            get
            {
                if (string.IsNullOrEmpty(_contentTypeName))
                    ReadConfiguration(Configuration);
                return _contentTypeName;
            }
            set
            {
                if (_contentTypeName != value)
                {
                    _contentTypeName = value;
                    _baseType = GetBaseType(_contentTypeName);
                    _contentType = null;
                }
            }
        }

        public ContentByTypeScope Scope
        {
            get { return _scope; }
            set { _scope = value; }
        }

        public string ListServerTemplate
        {
            get { return _listServerTemplate; }
            set { _listServerTemplate = value; }
        }


        public static bool IsWSS
        {
            get
            {
                Guid id = new Guid("F6924D36-2FA8-4f0b-B16D-06B7250180FA");
                foreach (SPFeature feature in SPContext.Current.SiteFeatures)
                    if (feature.DefinitionId == id)
                        return false;
                return true;
            }
        }

        /// <summary>
        /// Add a field to the Fields collection based on the definition in SharePoint (SPField).
        /// This 'translates' SPField properties to our own Field objects.
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        private IEnumerable<Field> AddSharePointField(SPField field)
        {

            if (field == null)
            {
                throw new Exception("AddSharePointField : field cannot be null.");
            }
            // Create new Field and set names.
            Field result = FieldFactory.GetField(field);
            yield return result;

            // Determine the field type and return an extra url field if required.
            switch (field.Type)
            {
                case SPFieldType.Lookup:
                    // For a lookup field, add a new field to the collection the has
                    // a hyperlink to the lookup item.
                    SPFieldLookup lookupField = (SPFieldLookup)field;

                    // Create the new lookuplink field.
                    Field lookupLinkField = new FieldUrl(GetLinkFieldName(result));
                    lookupLinkField.Name = lookupLinkField.Id;
                    lookupLinkField.DisplayName = string.Format("{0} Link (*)", result.DisplayName);
                    lookupLinkField.IsInternal = true;
                    // Add custom properties to this lookup link field to save ID of the lookup list and web.
                    lookupLinkField.Properties.Add(new CustomProperty("ListId", lookupField.LookupList));
                    lookupLinkField.Properties.Add(new CustomProperty("WebId", lookupField.LookupWebId.ToString("B")));
                    result.LinkFieldId = lookupLinkField.Id;

                    // return the lookup link field.
                    yield return lookupLinkField;
                    break;
                case SPFieldType.User:
                    // For a Person field, add a new field to the collection the has
                    // a hyperlink to the person's MySite, or Group details.
                    // For a Person field, add a new field to the collection the has
                    // a hyperlink to the person's MySite, or Group details.
                    Field userLinkField = new FieldUrl(GetLinkFieldName(result));
                    userLinkField.Name = userLinkField.Id;
                    userLinkField.DisplayName = string.Format("{0} Link (*)", result.DisplayName);
                    userLinkField.IsInternal = true;
                    result.LinkFieldId = userLinkField.Id;
                    yield return userLinkField;
                    break;
                case SPFieldType.File:
                    // File field retrun a field for the link to the document itself.
                    // a hyperlink to the person's MySite, or Group details.
                    Field fileLinkField = new FieldUrl(GetLinkFieldName(result));
                    fileLinkField.Name = fileLinkField.Id;
                    fileLinkField.DisplayName = string.Format("{0} Link (*)", result.DisplayName);
                    fileLinkField.IsInternal = true;
                    result.LinkFieldId = fileLinkField.Id;
                    yield return fileLinkField;
                    break;
            }
        }

        /// <summary>
        /// Overriden from the base datasource. Content type specific implementation that
        /// returns the referenced fields in the content type.
        /// </summary>
        /// <returns></returns>
        protected override IEnumerable<Field> LoadFields()
        {
            // Check if we have a content type selected.
            if ((!string.IsNullOrEmpty(ContentTypeName)) && (ContentTypeName != "-1"))
            {
                if (ContentType != null)
                {
                    // Always add a field with link to the list item.
                    FieldUrl fieldLinkUrl = new FieldUrl(DisplayLinkId);
                    fieldLinkUrl.Name = "LinkUrl";
                    fieldLinkUrl.DisplayName = "Link to item (*)";
                    fieldLinkUrl.IsInternal = true;
                    yield return fieldLinkUrl;

                    // Iterate through fields in the content type and return Field object.
                    for (int i = 0; i < ContentType.FieldLinks.Count; i++)
                    {
                        // Get the site column referenced in the content type.
                        SPField siteColumn = null;
                        siteColumn = Web.AvailableFields[ContentType.FieldLinks[i].Id];

                        // 'Translate' the site column to one or more Fields. 
                        // for lookup and person for example, multiple fields are returned.
                        //(field.Id == new Guid("{4dd7e525-8d6b-4cb4-9d3e-44ee25f973eb}"))//
                        if (!siteColumn.Hidden && siteColumn.Type != SPFieldType.Computed)
                        {
                            foreach (Field f in AddSharePointField(siteColumn))
                            {
                                yield return f;
                            }
                        }
                    }
                    foreach (Field f in GetDefaultField("Modified"))
                        yield return f;
                    foreach (Field f in GetDefaultField("Editor"))
                        yield return f;
                    foreach (Field f in GetDefaultField("Created"))
                        yield return f;
                    foreach (Field f in GetDefaultField("Author"))
                        yield return f;
                    foreach (Field f in GetDefaultField("ID"))
                        yield return f;
                    foreach (Field f in GetDefaultField("_ModerationStatus"))
                        yield return f;

                }
            }
        }

        private IEnumerable<Field> GetDefaultField(string name)
        {
            for (int i = 0; i < Web.AvailableFields.Count; i++)
            {
                try
                {
                    SPField field = Web.AvailableFields[i];
                    if (string.Compare(field.InternalName, name, true) == 0)
                    {
                        // Locale not (yet) required.
                        return AddSharePointField(field);
                    }
                }
                catch (KeyNotFoundException)
                {
                    //Error loading field; ignore.
                }
            }
            return null;
        }

        /// <summary>
        /// Get the data. Returns an empty datatable, if configuration is not complete.
        /// </summary>
        /// <returns>DataTable with typed data, based on the query that was setup by the user.</returns>
        public override System.Data.DataTable GetData()
        {
            // Validate.
            if (string.IsNullOrEmpty(ContentTypeName) || ContentTypeName == "-1")
                return new DataTable();
            if (ContentType == null)
                throw new DataSourceException(string.Format("Content type with name {0} could not be found.", ContentTypeName));
            if (_baseType == SPBaseType.UnspecifiedBaseType)
                return new DataTable();
            if ((Configuration == null) || (Configuration.Fields.Count == 0))
            {
                return new DataTable();
            }

            DataTable results = null;
            if (IsWSS)
            {
                results = GetWSSQueryResults();
            }
            else
            {
                results = GetMOSSQueryResults();
            }

            // transform the data and return results.
            return GetTypedData(results);
        }

        private DataTable GetWSSQueryResults()
        {
            string andStart = string.Empty;
            string andEnd = string.Empty;
            string filters = GetFilters(out andStart, out andEnd);

            SPSiteDataQuery siteQuery = new SPSiteDataQuery();
            siteQuery.Lists = GetLists();
            siteQuery.ViewFields = GetViewFields();
            siteQuery.Query = GetQuery(filters, andStart, andEnd);  
            siteQuery.RowLimit = GetRowLimit();
            siteQuery.Webs = GetWebs();
            DataTable results = Web.GetSiteData(siteQuery);
            
            _queryDebug.Add(string.Format("WSS QUERY - results:{0}", results.Rows.Count.ToString()));
            _queryDebug.Add(string.Format("ViewFields: {0}", siteQuery.ViewFields));
            _queryDebug.Add(string.Format("Lists: {0}", siteQuery.Lists));
            _queryDebug.Add(string.Format("Query: {0}", siteQuery.Query));
            _queryDebug.Add(string.Format("Webs: {0}", siteQuery.Webs));
            _queryDebug.Add(string.Format("RowLimit: {0}", siteQuery.RowLimit.ToString()));
            return results;
        }

        private DataTable GetMOSSQueryResults()
        {
            // Setup the query object.
            CrossListQueryInfo query = new CrossListQueryInfo();
            query.RowLimit = GetRowLimit();
            query.WebUrl = GetWebUrl();
            string andStart = string.Empty;
            string andEnd = string.Empty;
            string filters = GetFilters(out andStart, out andEnd);

            query.Query = GetQuery(filters, andStart, andEnd);
            query.Lists = GetLists();
            query.Webs = GetWebs();
            query.ViewFields = GetViewFields();

            // Submit the CAML query to the query cache.
            CrossListQueryCache xlqCache = new CrossListQueryCache(query);
            DataTable results = xlqCache.GetSiteData(SPContext.Current.Site);

            _queryDebug.Add(string.Format("MOSS QUERY - results:{0}", results.Rows.Count.ToString()));
            _queryDebug.Add(string.Format("ViewFields: {0}", query.ViewFields));
            _queryDebug.Add(string.Format("Lists: {0}", query.Lists));
            _queryDebug.Add(string.Format("Query: {0}", query.Query));
            _queryDebug.Add(string.Format("Webs: {0}", query.Webs));
            _queryDebug.Add(string.Format("WebUrl: {0}", query.WebUrl));
            _queryDebug.Add(string.Format("RowLimit: {0}", query.RowLimit.ToString()));
            return results;
        }

        protected virtual string GetQuery(string filters, string andStart, string andEnd)
        {
            string result = GetWhere(filters, andStart, andEnd);
            result += GetOrderBy();
            result += GetGroupBy();
            return result;
        }

        protected string GetViewFields()
        {
            // Add the selected fields to the CAML query.
            string viewFields = string.Empty;
            foreach (FieldReference fieldRef in Configuration.Fields)
            {
                if (Fields.Contains(fieldRef.Id))
                {
                    Field field = Fields[fieldRef.Id];
                    if (!field.IsInternal)
                        viewFields += AddViewField(viewFields, field.InternalId);
                    if (Field.IsFieldId(fieldRef.LinkFieldId) && Fields.Contains(fieldRef.LinkFieldId))
                    {
                        Field linkField = Fields[fieldRef.LinkFieldId];
                        if (linkField.IsInternal)
                        {
                            Field dataField = Fields.GetFirstFieldWithName(GetDataFieldName(linkField));
                            if (dataField != null && !dataField.IsInternal)
                            {
                                viewFields += AddViewField(viewFields, dataField.InternalId);
                            }
                        }
                        else
                        {
                            viewFields += AddViewField(viewFields, linkField.InternalId);
                        }
                    }
                }
            }
            viewFields += AddViewField(viewFields, "FileRef");
            viewFields += AddViewField(viewFields, "ID");
            if (Field.IsFieldId(Configuration.GroupBy) && Fields.Contains(Configuration.GroupBy))
                viewFields += AddViewField(viewFields, Fields[Configuration.GroupBy].InternalId);
            if (Field.IsFieldId(Configuration.SortBy) && Fields.Contains(Configuration.SortBy))
                viewFields += AddViewField(viewFields, Fields[Configuration.SortBy].InternalId);
            return viewFields;
        }

        protected string GetOrderBy()
        {
            // Add the selected fields to the CAML query.
            string orderbyField = string.Empty;
            string groupbyField = string.Empty;
            if (Field.IsFieldId(Configuration.GroupBy) && Fields.Contains(Configuration.GroupBy))
            {
                Guid id = Guid.Empty;
                if (GuidUtil.GuidTryParse(Fields[Configuration.GroupBy].InternalId, out id))
                {
                    groupbyField = string.Format("<FieldRef ID=\"{0}\" />", id.ToString("B"));
                }
                else
                {
                    groupbyField = string.Format("<FieldRef Name=\"{0}\" />", Fields[Configuration.GroupBy].Name);
                }
            }
            if (Field.IsFieldId(Configuration.SortBy) && Fields.Contains(Configuration.SortBy))
            {
                string sortOrder = string.Empty;
                if (Configuration.SortOrder.ToLower() == "desc")
                {
                    sortOrder += "Ascending=\"FALSE\"";
                }
                Guid sortid = Guid.Empty;
                if (GuidUtil.GuidTryParse(Fields[Configuration.SortBy].InternalId, out sortid))
                {
                    orderbyField = string.Format("<FieldRef ID=\"{0}\" {1}/>", sortid.ToString("B"), sortOrder);
                }
                else
                {
                    orderbyField = string.Format("<FieldRef Name=\"{0}\" {1}/>", Fields[Configuration.SortBy].Name, sortOrder);
                }
            }
            return string.Format("<OrderBy>{0}{1}</OrderBy>", groupbyField, orderbyField);
        }

        protected string GetGroupBy()
        {
            string groupBy = string.Empty;
            return groupBy;
        }

        private string GetWebs()
        {
            switch (Scope)
            {
                case ContentByTypeScope.CurrentWeb:
                    return string.Empty;
                case ContentByTypeScope.CurrentWebAndBelow:
                    return "<Webs Scope=\"Recursive\" />";
                case ContentByTypeScope.SiteCollection:
                    return "<Webs Scope=\"SiteCollection\" />";
            }
            return string.Empty;
        }

        private string GetLists()
        {
            if (string.IsNullOrEmpty(ListServerTemplate) || ListServerTemplate == "-1")
                return string.Format("<Lists BaseType=\"{0}\" Hidden=\"FALSE\" MaxListLimit=\"0\"/>", ((int)_baseType).ToString());
            else
                return string.Format("<Lists ServerTemplate=\"{0}\" Hidden=\"FALSE\" MaxListLimit=\"0\"/>", ListServerTemplate);
        }

        private string GetWhere(string filters, string andStart, string andEnd)
        {
            string where = string.Empty;
            if (_includeDescending)
            {
                where = string.Format("<Where>{2}<BeginsWith><FieldRef Name=\"ContentTypeId\" /><Value Type=\"Text\">{0}</Value></BeginsWith>{1}{3}</Where>", ContentType.Id, filters, andStart, andEnd);
            }
            else
            {
                where = string.Format("<Where>{2}<Eq><FieldRef Name=\"ContentType\" /><Value Type=\"Text\">{0}</Value></Eq>{1}{3}</Where>", ContentType.Name, filters, andStart, andEnd);
                // current user
                //where = string.Format("<Where><And><Eq><FieldRef Name=\"Editor\" /><Value Type=\"User\"><UserID/></Value></Eq><Eq><FieldRef Name=\"ContentType\" /><Value Type=\"Text\">{0}</Value></Eq></And></Where>", ContentType.Name);
                // brian wilson
                // http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1928048&SiteID=1
                //where = string.Format("<Where><And><Eq><FieldRef Name=\"Editor\" /><Value Type=\"User\">Brian Wilson</Value></Eq><Eq><FieldRef Name=\"ContentType\" /><Value Type=\"Text\">{0}</Value></Eq></And></Where>", ContentType.Name);
            }
            return where;
        }

        private string GetFilters(out string andStart, out string andEnd)
        {
            // Get filters
            string filters = string.Empty;
            andEnd = string.Empty;
            andStart = string.Empty;
            filters = FiltersToCAML.GetCAML(Configuration.Filters);
            if (!string.IsNullOrEmpty(filters))
            {
                andStart = "<And>";
                andEnd = "</And>";
            }
            return filters;
        }

        private string GetWebUrl()
        {
            // Set the weburl based on the scope
            if (Scope == ContentByTypeScope.SiteCollection)
                return SPContext.Current.Site.ServerRelativeUrl;
            else
                return Web.ServerRelativeUrl;
        }

        private uint GetRowLimit()
        {
            if (Configuration.MaximumItems > 0)
                return (uint)Configuration.MaximumItems;
            else
                return uint.MaxValue;
        }

        /// <summary>
        /// Add a field to the CAML query ViewFields. Check if field is not yet added.
        /// </summary>
        /// <param name="viewFields"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private string AddViewField(string viewFields, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return string.Empty;
            }
            // no encoding of name required. these are always internal SharePoint names.
            Guid id = Guid.Empty;
            string newField = string.Empty;
            if (GuidUtil.GuidTryParse(name, out id))
            {
                newField = string.Format("<FieldRef ID=\"{0}\" />", id.ToString("B"));
            }
            else
            {
                newField = string.Format("<FieldRef Name=\"{0}\" />", name);
            }
            if ((string.IsNullOrEmpty(viewFields)) || (!viewFields.Contains(newField)))
            {
                return newField;
            }
            return string.Empty;
        }

        /// <summary>
        /// Transform query results to data that has the correct datatypes. Query results are
        /// just plain strings by default. Transform to the datatype defined by the site column.
        /// </summary>
        /// <param name="table"></param>
        /// <returns></returns>
        protected DataTable GetTypedData(DataTable table)
        {
            // Create new table for the results.
            DataTable newTable = new DataTable();

            // Add all internal fields.
            foreach (Field internalField in Fields)
            {
                if ((internalField.IsInternal) && (!newTable.Columns.Contains(internalField.Name)))
                {
                    bool add = false;
                    foreach (FieldReference configField in Configuration.Fields)
                    {
                        if (string.Compare(configField.LinkFieldId, internalField.Id, true) == 0)
                        {
                            add = true;
                            break;
                        }
                    }
                    if (add)
                        newTable.Columns.Add(internalField.Name, internalField.DataType);
                }
            }

            // Add all selected fields (by the user).
            foreach (FieldReference fieldRef in Configuration.Fields)
            {
                if (Fields.Contains(fieldRef.Id))
                {
                    Field field = Fields[fieldRef.Id];
                    if (!newTable.Columns.Contains(field.Name))
                    {
                        newTable.Columns.Add(field.Name, field.DataType);
                    }
                }
            }

            // Add the field for grouping the data.
            if ((Field.IsFieldId(Configuration.GroupBy)) && (!newTable.Columns.Contains(Fields[Configuration.GroupBy].Name)))
            {
                newTable.Columns.Add(Fields[Configuration.GroupBy].Name, Fields[Configuration.GroupBy].DataType);
            }

            // Add the field for sorting the data.
            if ((Field.IsFieldId(Configuration.SortBy)) && (!newTable.Columns.Contains(Fields[Configuration.SortBy].Name)))
            {
                newTable.Columns.Add(Fields[Configuration.SortBy].Name, Fields[Configuration.SortBy].DataType);
            }

            // Add the siteid field. This field is always returned by the SharePoint query mechanism.
            if (!newTable.Columns.Contains("SiteId"))
            {
                newTable.Columns.Add("SiteId", typeof(string));
            }

            // Add the webid field. This field is always returned by the SharePoint query mechanism.
            if (!newTable.Columns.Contains("WebId"))
            {
                newTable.Columns.Add("WebId", typeof(string));
            }

            // Add the listid field. This field is always returned by the SharePoint query mechanism.
            if (!newTable.Columns.Contains("ListId"))
            {
                newTable.Columns.Add("ListId", typeof(string));
            }

            // Add the ID field.
            if (!newTable.Columns.Contains("ID"))
            {
                newTable.Columns.Add("ID", typeof(Int32));
            }

            // Add the LinkUrl field.
            if (!newTable.Columns.Contains("LinkUrl"))
            {
                newTable.Columns.Add("LinkUrl", typeof(Uri));
            }

            // Iterate through resultset of the query and populate our own result set.
            foreach (DataRow row in table.Rows)
            {
                // Add a new row to the result set.
                DataRow newRow = newTable.NewRow();

                // Populate the field that has a link to the details page of the item.
                Guid listID = new Guid(row["ListId"].ToString());
                Guid webID = new Guid(row["WebId"].ToString());
                
                newRow["LinkUrl"] = new Uri(string.Format("{0}/{1}", SPContext.Current.Site.Url, string.Format(DisplayFormUrl, row["ID"], listID.ToString("B"), webID.ToString("B"), SPContext.Current.Site.ID.ToString("B"))));

                // Populate ID fields for web, list and site.
                newRow["SiteId"] = SPContext.Current.Site.ID.ToString("B");
                newRow["WebId"] = webID.ToString("B");
                newRow["ListId"] = listID.ToString("B");
                newRow["ID"] = row["ID"];
                foreach (DataColumn col in newTable.Columns)
                {
                    if (newRow[col.ColumnName].GetType() == typeof(System.DBNull))
                    {
                        SetFieldValue(Fields.GetFirstFieldWithName(col.ColumnName), newRow, row);
                    }
                }
                newTable.Rows.Add(newRow);
            }
            return newTable;
        }

        private void SetFieldValue(Field field, DataRow newRow, DataRow row)
        {
            if (!field.IsInternal)
            {
                object fieldValue = row[field.DatasetId];
                if (!string.IsNullOrEmpty(fieldValue.ToString()))
                {
                    newRow[field.Name] = field.GetTypedValue(fieldValue);
                }
            }
            else
            {
                string dataName = GetDataFieldName(field);
                if (!string.IsNullOrEmpty(dataName))
                {
                    Field dataField = Fields.GetFirstFieldWithName(dataName);
                    object dataValue = row[dataField.DatasetId];
                    if (dataField != null && dataValue != null)
                    {
                        switch (dataField.FieldType)
                        {
                            case FieldType.Person:
                                // generate the link for the link field.
                                newRow[field.Name] = GetLinkForUser(dataValue.ToString());
                                break;
                            case FieldType.Lookup:
                                // generate the link for the link field.
                                //Field linkField = Fields[GetLinkFieldName(field)];
                                newRow[field.Name] = GetLinkForLookup(dataValue.ToString(), field);
                                break;
                            case FieldType.File:
                                // generate the link for the file.
                                newRow[field.Name] = GetLinkForFile(row["FileRef"].ToString());
                                //newRow[field.Name] = GetLinkForFile(dataValue.ToString());
                                break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// For some field types (lookup, person) an extra field is added automatically.
        /// This returns the name for these link fields.
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        private static string GetLinkFieldName(Field field)
        {
            if (field == null)
            {
                return string.Empty;
            }
            return string.Format("{0}_Url", field.Name);
        }

        /// <summary>
        /// Generate the url to the user or usergroup.
        /// </summary>
        /// <param name="userLink"></param>
        /// <returns></returns>
        private static Uri GetLinkForUser(string userLink)
        {
            if (string.IsNullOrEmpty(userLink))
            {
                return null;
            }
            if (userLink.IndexOf(";#") > 0)
            {
                string id = userLink.Substring(0, userLink.IndexOf(";#"));
                string siteUrl = SPContext.Current.Site.ServerRelativeUrl;
                if (siteUrl == "/")
                    siteUrl = string.Empty;
                return new Uri(string.Format("{0}/{1}", siteUrl, string.Format(UserLinkUrl, id)), UriKind.Relative);

            }
            else
            {
                throw new DataSourceException(string.Format("{0} is not a valid SharePoint link value", userLink));
            }
        }

        /// <summary>
        /// Generate the url to the file.
        /// </summary>
        /// <param name="fileLink"></param>
        /// <returns></returns>
        private static Uri GetLinkForFile(string fileLink)
        {
            if (string.IsNullOrEmpty(fileLink))
            {
                return null;
            }
            if (fileLink.IndexOf(";#") > 0)
            {
                return new Uri(SPContext.Current.Site.MakeFullUrl("/" + StripID(fileLink)));
            }
            else
            {
                throw new DataSourceException(string.Format("{0} is not a valid SharePoint file value", fileLink));
            }
        }

        /// <summary>
        /// Generate a link to a lookup item.
        /// </summary>
        /// <param name="lookupLink"></param>
        /// <param name="field"></param>
        /// <returns></returns>
        private static Uri GetLinkForLookup(string lookupLink, Field field)
        {
            if (field == null)
            {
                throw new DataSourceException("GetLinkForLookup: field parameter cannot be null.");
            }
            if (lookupLink == null || lookupLink.IndexOf(";#") == 0)
            {
                // TST - test.
                throw new DataSourceException(string.Format("GetLinkForLookup: {0} is not a valid SharePoint link value", lookupLink));
            }
            string listId = field.Properties["ListId"].Value;
            string webId = field.Properties["WebId"].Value;
            if (string.IsNullOrEmpty(listId) || string.IsNullOrEmpty(webId))
                return new Uri(SPContext.Current.Site.Url);
            string id = lookupLink.Substring(0, lookupLink.IndexOf(";#"));

            string siteUrl = SPContext.Current.Site.ServerRelativeUrl;
            if (siteUrl == "/")
                siteUrl = string.Empty;
            return new Uri(string.Format("{0}/{1}", siteUrl, string.Format(DisplayFormUrl, id, listId, webId, SPContext.Current.Site.ID.ToString("B"))), UriKind.Relative);
        }

        /// <summary>
        /// Translate SPBaseType to a string value that the query object understands.
        /// </summary>
        /// <param name="baseType"></param>
        /// <returns></returns>
        private SPBaseType GetBaseType(string contentTypeName)
        {
            if (string.IsNullOrEmpty(contentTypeName) || ContentType==null)
                return SPBaseType.UnspecifiedBaseType;
            // Unused = 2 is not used.
            // Survey = 4 is not used.
            if (ContentType.Id.ToString().ToLower().StartsWith("0x0103"))
                return SPBaseType.Issue;
            else if (ContentType.Id.ToString().ToLower().StartsWith("0x012002"))
                return SPBaseType.DiscussionBoard;
            else if (ContentType.Id.ToString().ToLower().StartsWith("0x0101"))
                return SPBaseType.DocumentLibrary;
            else
                return SPBaseType.GenericList;
        }

        /// <summary>
        /// Get the custom property values of the selected content type and base type.
        /// These are set by the editor part for this datasource.
        /// </summary>
        /// <param name="configuration"></param>
        public override void ReadConfiguration(DatasourceConfiguration configuration)
        {
            if (configuration != null)
            {
                if (configuration.CustomProperties.Contains("contenttype"))
                {
                    _contentTypeName = configuration.CustomProperties["contenttype"].Value;
                    _baseType = GetBaseType(_contentTypeName);
                }
                if (configuration.CustomProperties.Contains("includedescending"))
                    _includeDescending = (configuration.CustomProperties["includedescending"].Value.ToLower()=="true");
                if (configuration.CustomProperties.Contains("scope"))
                {
                    int scope = 0;
                    if (!string.IsNullOrEmpty(configuration.CustomProperties["scope"].Value) && int.TryParse(configuration.CustomProperties["scope"].Value, out scope))
                        _scope = (ContentByTypeScope)scope;
                }
                if (configuration.CustomProperties.Contains("listservertemplate"))
                    _listServerTemplate = configuration.CustomProperties["listservertemplate"].Value;

            }
        }

        private static string StripID(string value)
        {
            if (value == null)
                return null;
            return Regex.Replace(value, "^[0-9]+;#", "");
        }

        /// <summary>
        /// For some field types (lookup, person) an extra field is added automatically.
        /// This returns the name of the data field for these link fields.
        /// </summary>
        /// <param name="field"></param>
        /// <returns></returns>
        private static string GetDataFieldName(Field field)
        {
            if (field == null || !field.IsInternal || !field.Name.EndsWith("_Url"))
            {
                return string.Empty;
            }
            return field.Name.Replace("_Url", string.Empty);
        }

    }

    public enum ContentByTypeScope
    {
        CurrentWeb,
        CurrentWebAndBelow,
        SiteCollection
    }

}
